Backlog の Git リポジトリをソースとした Docker イメージのビルドパイプラインを作ってみた

Backlog の Git リポジトリをソースとした Docker イメージのビルドパイプラインを作ってみた

「Backlog Git から Docker イメージをビルドするには」というお題でCI/CDパイプラインを考えてみました。
Clock Icon2023.02.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは!AWS事業本部コンサルティング部のたかくに(@takakuni_)です。

今回は、Backlog の Git リポジトリ(以後、Backlog Git)をソースとして、 Docker イメージをビルドパイプラインを作ってみようと思います。

コミットIDをイメージタグにしたい

皆さんは Backlog Git のコミットID を Docker イメージのタグとして利用したいなと思ったことはありますか?私はあります。

CodePipeline のソースに、Backlog Git が指定できないな...でも、Backlog Git で CI/CD をやってみたいな」という方もいらっしゃるのではないでしょうか。

では、さっそくやってみようと思います。

構成図

構成図は次の通りです。

また、ざっくりした仕組みは以下になります。

  1. Backlog Git の WebフックURL に API Gateway の URL を指定し、 push 時に API リクエストを送信
  2. API Gateway は CodeBuild プロジェクトを開始させ、 CodeBuild は Backlog Git に対してgit clone
  3. クローンした成果物を元に Docker イメージのビルド、 ECR へのプッシュ、アーティファクトの配置
  4. アーティファクトの配置をトリガーに CodePipeline を開始する
  5. CodeDeploy が項番4のアーティファクトを元に ECS へデプロイ

コード

今回利用したコードは以下に格納されています。

適宜実装したい環境に合わせてコードの変更してご利用いただければ幸いです。

https://github.com/takakuni-classmethod/backlog-git-docker-build

ここからは、どのような実装であるか解説していこうと思います。

Backlog から API Gateway の部分

Backlog では git push 時に指定した URL に POST リクエストを行う Webフック機能が提供されています。

API Gateway で POST リクエストを受け付け、後続の処理を行うように作ってみました。

https://backlog.com/ja/enterprise-help/userguide/userguide1581/

API Gatewayの実装

CodeBuild へのリクエスト

API Gateway は、 CodeBuild に HTTP API を利用して StartBuild アクションを実行します。

どの CodeBuild のプロジェクトを開始するのか」を指定するには、リクエストで利用する HTTP ヘッダー周りに追加で2つの設定が必要です。

  1. HTTP ヘッダーにX-Amz-Target: 'CodeBuild_20161006.StartBuild',Content-Type: 'application/x-amz-json-1.1'を含める
  2. Content-Type: 'application/x-www-form-urlencoded'に対して、{"projectName": "CodeBuildのプロジェクト名"}を含め開始するプロジェクトを指定

https://aws.amazon.com/jp/premiumsupport/knowledge-center/api-gateway-proxy-integrate-service/

IP制限

Backlog Git が利用する IP は公開されているため、気持ちばかりの IP 制限を API Gateway に付与してあげました。

Backlog ヘルプセンター Webhook 送信サーバーの IP アドレスを教えてください

ただ、以下のようにアドレスの変更もあるため、IP制限をかける / かけないは注意が必要です。

https://backlog.com/ja/product-updates/announcement/add-git-webhook-ips/

なお、 IP アドレス制限は AWS WAF ではなくリソースベースポリシーを利用しています。

https://dev.classmethod.jp/articles/api-gateway-resouce-policy-ip-address/

ログ

今回はテスト用のためログ記録を有効化していませんが、実行ログを取るのもオススメです。

「AWS Foundational Security Best Practices コントロール」でも、ログ記録を有効にするよう推奨されているため、ぜひ有効化してみてください。

https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-apigateway-1

CodeBuild の実装

buildspec

CodeBuild で利用する buildspec は以下の通りです。

  1. git-remote-codecommitをインストール
  2. 非対話形式に SSH でログインするため、 Backlog のホスト情報を登録
  3. Backlog Git に SSH で git clone
  4. Docker イメージタグに Git のコミットID(Backlog GUI と同じ桁数)を利用
  5. Docker イメージのビルド、 ECR へのプッシュ
  6. Blue/Green デプロイメントのためにアーティファクト作成およびオブジェクト配置
  7. (オプション)S3にgit-remote-codecommitのキャッシュ配置
buildspec.yaml
version: 0.2
env:
  variables:
    DOCKER_BUILDKIT: "1"
    AWS_PAGER: ""

phases:
  install:
    commands:
      - pip3 install git-remote-codecommit
  pre_build:
    commands:
      - eval $(ssh-agent)
      - ssh-add - <<< "$BACKLOG_GIT_CREDENTIAL"
      - ssh-keyscan $BACKLOG_SPACE_ID.git.$BACKLOG_DOMAIN_NAME >> ~/.ssh/known_hosts
  build:
    commands:
      - echo Cloning repository ...
      - git clone $BACKLOG_SPACE_ID@$BACKLOG_SPACE_ID.git.$BACKLOG_DOMAIN_NAME:/$BACKLOG_PROJECT_KEY/$BACKLOG_REPOSITORY_NAME.git
      - cd $BACKLOG_REPOSITORY_NAME
      - COMMIT_HASH=$(git log -n 1 --pretty=format:"%H" | cut -c 1-10)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
      - echo Build started on `date`
      - echo Building Docker image...
      - docker image build -f docker/Dockerfile -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPOSITORY_NAME:$IMAGE_TAG docker
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
      - echo Pushing Docker image...
      - docker image push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPOSITORY_NAME:$IMAGE_TAG
      - printf '{"ImageURI":"%s"}' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPOSITORY_NAME:$IMAGE_TAG > $CODEBUILD_SRC_DIR/imageDetail.json
      - cd codesries_asset
      - sed -i -e "s#<TASK_FAMILY>#${TASK_FAMILY}#" taskdef.json
      - sed -i -e "s#<TASK_EXECUTION_ROLE_ARN>#${TASK_EXECUTION_ROLE_ARN}#" taskdef.json
      - sed -i -e "s#<CONTAINER_NAME>#${CONTAINER_NAME}#" taskdef.json
      - sed -i -e "s#<AWS_DEFAULT_REGION>#${AWS_DEFAULT_REGION}#" taskdef.json
      - sed -i -e "s#<LOG_GROUP_NAME>#${LOG_GROUP_NAME}#" taskdef.json
      - sed -i -e "s#<LOG_STREAM_PREFIX>#${LOG_STREAM_PREFIX}#" taskdef.json
      - sed -i -e "s#<CONTAINER_NAME>#${CONTAINER_NAME}#" appspec.yaml
      - cp -p taskdef.json $CODEBUILD_SRC_DIR/
      - cp -p appspec.yaml $CODEBUILD_SRC_DIR/
artifacts:
  files:
    - imageDetail.json
    - taskdef.json
    - appspec.yaml

cache:
  paths:
    - /root/.cache/pip/**/*

Backlog の認証情報

CodeBuild では Backlog Git に対して SSH でgit cloneするように設定しました。

Backlog Git へのgit cloneで利用するパラメーターとして以下を利用しました。

  • スペースID
  • ドメイン
  • プロジェクトキー
  • レポジトリ名
  • 秘密鍵

パラメーターの数が多いため、 Key/Value 形式で取得できる Secrets Manager を利用して1つのシークレットにまとめて保管することにしました。

閲覧権限を分けたいやコスト要件もあるため、実際は Parameter Store や別シークレットで分けて管理もいいと思います。

code_pipeline.tf
resource "aws_secretsmanager_secret" "backlog_info" {
  name                    = "${local.prefix}/backlog_info"
  recovery_window_in_days = 0
  policy = templatefile("${path.module}/iam_policy_document/resource_secretsmanager.json", {
    role_arn = aws_iam_role.codebuild.arn
  })
}

resource "aws_secretsmanager_secret_version" "backlog_info" {
  secret_id = aws_secretsmanager_secret.backlog_info.id
  secret_string = jsonencode({
    space_id        = var.backlog.space_id
    domain_name     = var.backlog.domain_name
    project_key     = var.backlog.project_key
    repository_name = var.backlog.repository_name
    ssh_key         = tls_private_key.backlog.private_key_pem
  })

  lifecycle {
    ignore_changes = [
      secret_string
    ]
  }
}

CodePipeline

後続の CodePipeline は S3 をトリガーにパイプラインの開始を行いました。

当初、 ECR へのプッシュをトリガーにしようと考えていましたが、設定パラメータを見るとイメージタグを固定する必要があり断念しました。

(buildspec でイメージタグを書き換えるパターンもありですが、なるべく仕組みをシンプルにしたい結果、 S3 オブジェクト配置をトリガーにしてみました。)

ImageTag
必須 いいえ

イメージに使用するタグ。
>
注記
ImageTag の値を指定しない場合、デフォルト値は latest になります。

AWS CodePipeline ユーザーガイド Amazon ECR

今回のコードを利用してみて、オブジェクトを配置したが CodePipeline がトリガーされない方は以下を併せてご覧ください。

https://dev.classmethod.jp/articles/codepipeline-s3-trigger-hooked-on/

動作検証

では実際に動作検証してみましょう。

Terraform

variables.tfを確認するといくつか変数が定義されています。変数backlogにあたる部分を埋めます。

variables.tf
variable "system" {
  type    = string
  default = "backlog"
}

variable "env" {
  type    = string
  default = "cicd"
}

variable "cidr" {
  default = {
    vpc       = "192.168.0.0/16"
    public_a  = "192.168.0.0/24"
    public_c  = "192.168.1.0/24"
    private_a = "192.168.2.0/24"
    private_c = "192.168.3.0/24"
  }
}

variable "backlog" {
  default = {
    space_id        = "" # htts://sample.backlog.jp の sample の部分
    domain_name     = "" # backlog.jp または backlog.com
    project_key     = "" # https://backlog.com/ja/enterprise-help/userguide/userguide353/
    repository_name = "" # Backlog Git のレポジトリ名
    webhook_ip = [
      "54.248.107.22/32",
      "54.248.105.89/32",
      "54.238.168.195/32",
      "52.192.66.90/32",
      "54.65.251.183/32",
      "54.250.148.49/32",
      "35.166.55.243/32",
      "50.112.242.159/32",
      "52.199.112.83/32",
      "35.73.201.244/32",
      "35.72.166.154/32",
      "35.73.143.41/32",
      "35.74.201.20/32",
      "52.198.115.185/32",
      "35.165.230.177/32",
      "18.236.6.123/32",
    ] # https://support-ja.backlog.com/hc/ja/articles/360035645534-Webhook-%E9%80%81%E4%BF%A1%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%AE-IP-%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E3%82%92%E6%95%99%E3%81%88%E3%81%A6%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84
  }
}

準備ができたら Terraform でデプロイします。

terraform init
terraform plan
terraform apply

Backlog への公開鍵の登録

今回 Terraform の tls_private_keylocal_fileリソースを利用して、秘密鍵を Secrets Manager 、公開鍵をローカルに保管するように設定しています。

ローカルに保管された公開鍵を Backlog に登録します。

https://backlog.com/ja/git-tutorial/reference/ssh/

Webhook URL の登録

API Gateway コンソールから API リクエストを送るための URL を取得します。

取得した URL を利用して、 WebフックURL を登録します。

https://backlog.com/ja/enterprise-help/userguide/userguide1291/

Backlog Git へのソースコードの配置

GitHub に上がっている次のフォルダ(codesries_assetdocker)を Backlog Git レポジトリに配置します。

BACKLOG_GIT_REPO
├── codesries_asset
│   ├── appspec.yaml
│   └── taskdef.json
└── docker
    └── Dockerfile

codesries_assetフォルダでは、Blue Green デプロイのためのファイル。dockerフォルダは Docker イメージを作成するためのファイルが格納されいます。

このフォルダ周りの構成を変えたい場合は、 buildspec と調整しながらカスタマイズしてください。

cp -pR backlog-git-docker-build/BACKLOG_GIT_REPO/* {BACKLOG_GITのレポジトリ名}/
cd {BACKLOG_GITのレポジトリ名}/
git add .
git commit -m 'ADD docker image build asset.'
git push

Docker イメージを確認

コードのプッシュが終わると CodeBuild が起動していると思います。プッシュから時間が立っていなければ、ステータスが進行中かと思います。

ステータスが成功になると、 ECR を見てみましょう。初めの要件であった、Backlog Git のコミットID と ECR のイメージタグが一致してると思います。

Backlog Git 側

ECR 側

※ イメージタグ first は、 Terraform の初回デプロイで利用したイメージになります。

Blue/Green デプロイ

少し忙しいですが、後続の CodePipeline が起動していると思います。

テストポートへの新バージョンの配置は5分間で設定しているため、必要に応じて時間を調節してください。

後片付け

後片付けが必要な方は、backlog-git-docker-buildフォルダに移動後、terraform destroyで片付け可能です。

cd backlog-git-docker-build
terraform destroy

Backlog に登録した公開鍵、WebフックURLも必要に応じて削除してください。

参考

今回、以下のブログを大変参考にさせていただきました!ありがとうございます!

https://engineers.fenrir-inc.com/entry/mirror-backlog-repository-to-codecommit

まとめ

以上、「Backlog の Git リポジトリをソースとした Docker イメージのビルドパイプラインを作ってみた」でした。

実際にテストして動きをご確認いただいた方が、イメージつきやすいと思うのでぜひお試しいただけると嬉しいです。

この記事がどなたかの参考になれば幸いです。

AWS事業本部コンサルティング部のたかくに(@takakuni_)でした!

この記事をシェアする

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.